/**
 * @file ggs.c
 *
 *  A ggs client in C language adapted from kitty's
 *	GGS_client.C (in C++) by Igord Durdanovic.
 *
 * TODO: all this code should be rewritten!
 *
 * @date 2002 - 2011
 * @author Richard Delorme
 * @version 4.1
 */

#if defined(__unix__) || defined(__APPLE__)

#include "ggs.h"
#include "board.h"
#include "move.h"
#include "util.h"
#include "ui.h"

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <stdarg.h>

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <netinet/tcp.h>

#define is_empty(word) (*(word) == '\0')

static Log ggs_log[1];

static char admin_list[][16] = {"delorme", "dan", "mic", "romano", "HCyrano", "romelica", ""};

const char error_message[32][64] = {
	"GGS_PARSE_OK",
	"GGS_PARSE_BAD_INT",
	"GGS_PARSE_BAD_INT_RANGE",
	"GGS_PARSE_BAD_DOUBLE",
	"GGS_PARSE_BAD_DOUBLE_RANGE",
	"GGS_PARSE_BAD_MOVE",
	"GGS_PARSE_BAD_TIME",
	"GGS_PARSE_BAD_CLOCK",
	"GGS_PARSE_BAD_PLAYER",
	"GGS_PARSE_BAD_REQUEST",
	"GGS_PARSE_BAD_MATCH_ON",
	"GGS_PARSE_BAD_MATCH_OFF",
	"GGS_PARSE_BAD_OS_ON",
	"GGS_PARSE_BAD_OS_OFF",
	"GGS_PARSE_BAD_BOARD"
	"GGS_PARSE_BAD_ADMIN"
	"GGS_PARSE_BAD_SAIO_DELAY"
};

/* copy a string with dynamic memory reallocation */
void ggs_string_copy(char **dest, const char *src){
	int len = strlen(src) + 1;
	*dest = realloc(*dest, len);
	if (*dest == NULL){
		fatal_error("cannot reallocate the string\n");
	}
	strcpy(*dest, src);
}

/* init GGSText */
void ggs_text_init(GGSText *text){
	text->n_lines = 0;
}

/* add lines to GGSText */
int ggs_text_add(GGSText *text, const char *buffer){
	const char *i;
	char *o = text->line[text->n_lines];
	int line_size;

	if (text->n_lines == TEXT_SIZE) return 0;

	*o='\0';
	line_size = 0;
	for (i = buffer; *i; i++){
		if (*i == '\n' || line_size == LINE_SIZE - 1){
			*o = '\0';
			o = text->line[++text->n_lines];
			line_size = 0;
			if (text->n_lines == TEXT_SIZE) return 0;
			if (*i == '\n') continue;
		}
		*(o++) = *i;
		line_size++;
	}
	*o = '\0';
	if (line_size > 0) text->n_lines++;
	return 1;
}

/* print a GGStext */
void ggs_text_print(GGSText *text, FILE *f){
	int i;
	for (i = 0; i < text->n_lines; i++)
		fprintf(f, "%3d:%s\n", i, text->line[i]);
}

int ggs_is_space(int c) {
	return isspace(c) || c == '(' || c == ')';
}

/* parse a line until it finds a valid word */
const char *ggs_parse(const char *line, char *word){
	while (!ggs_is_space(*line) && *line) *(word++) = *(line++);
	*word = '\0';
	while (ggs_is_space(*line) && *line) line++;

	return line;
}

/* skip a word */
const char *ggs_skip_word(const char *line){
	while (!ggs_is_space(*line) && *line) line++;
	while (ggs_is_space(*line) && *line) line++;
	return line;
}

/* parse a line until it finds a special character*/
const char *ggs_parse_until(const char *line, char *word, const char mark){
	while (!(*line == mark) && *line) *(word++) = *(line++);
	*word = '\0';
	if (*line == mark) line++;

	return line;
}

/* parse a line until it finds a special character*/
const char *ggs_skip_until(const char *line, const char mark){
	while (!(*line == mark) && *line) line++;
	if (*line == mark) line++;

	return line;
}

/* translate input word into an integer */
int ggs_int_parse(int *value, const char *word){

	errno = 0;
	*value = string_to_int(word, 0);
	if (errno == EINVAL){
		error("%s is not a valid integer.\n", word);
		return GGS_PARSE_BAD_INT;
	} else if (errno == ERANGE){
		error("%s is out of integer range.\n", word);
		return GGS_PARSE_BAD_INT_RANGE;
	}

	return GGS_PARSE_OK;
}

/* translate input word into a double */
int ggs_double_parse(double *value, const char *word){
	errno = 0;
	*value = string_to_real(word, 0.0);
	if (errno == EINVAL){
		error("%s is not a valid double.\n", word);
		return GGS_PARSE_BAD_DOUBLE;
	}
	if (errno == ERANGE){
		error("%s is out of double range.\n", word);
		return GGS_PARSE_BAD_DOUBLE_RANGE;
	}

	return GGS_PARSE_OK;
}

int ggs_move_parse(int *move, const char *word){
	*move = string_to_coordinate(word);
	if (*move == NOMOVE) {
		error("%s is not a valid move\n", word);
		return GGS_PARSE_BAD_MOVE;
	}
	return GGS_PARSE_OK;
}

/* translate input into a time (as seconds in double precision) */
int ggs_time_parse(double *time, const char *line){
	errno = 0;
	*time = 0.001 * string_to_time(line);
	if (errno) {
		return GGS_PARSE_BAD_TIME;
	}

	return GGS_PARSE_OK;
}

/* concisely print time */
void ggs_time_print(double t, FILE *f){
	int h, m, s;

	h = (int)(t / 3600.0);
	m = ((int)(t / 60.0)) % 60;
	s = ((int)t) % 60;

	if (h > 0)
		fprintf(f, "%d:%02d:%02d", h, m, s);
	else if (m > 0)
		fprintf(f, "%d:%02d", m, s);
	else
		fprintf(f, "%d", s);
}

#define RETURN(return_value){ \
	free(time); \
	free(word); \
	return return_value; \
}

/* ggs parse clock */
int ggs_clock_parse(GGSClock *ggsclock, const char *line){
	int l = strlen(line) + 1;
	char *time = malloc(l * sizeof(char));
	const char *move;
	char *word = malloc(l * sizeof(char));

	memset(ggsclock, 0, sizeof(GGSClock));

	line = ggs_parse_until(line, word, '/');
	if (!is_empty(word)){
		move = ggs_parse_until(word, time, ',');
		if (ggs_time_parse(&ggsclock->ini_time, time))
			RETURN(GGS_PARSE_BAD_CLOCK);
		if (!is_empty(move)){
			if (*move == 'N'){
				ggsclock->ini_flag = 1;
				move++;
			}
			if (ggs_int_parse(&ggsclock->ini_move, move))
				RETURN(GGS_PARSE_BAD_CLOCK);
		}
	}

	line = ggs_parse_until(line, word, '/');
	if (!is_empty(word)){
		move = ggs_parse_until(word, time, ',');
		if (ggs_time_parse(&ggsclock->inc_time, time))
			RETURN(GGS_PARSE_BAD_CLOCK);
		if (!is_empty(move)){
			if (*move == 'N'){
				ggsclock->inc_flag = 1;
				move++;
			}
			if (ggs_int_parse(&ggsclock->inc_move, move))
				RETURN(GGS_PARSE_BAD_CLOCK);
		}
	}

	line = ggs_parse_until(line, word, '\0');
	if (!is_empty(word)){
		move = ggs_parse_until(word, time, ',');
		if (ggs_time_parse(&ggsclock->ext_time, time))
			RETURN(GGS_PARSE_BAD_CLOCK);
		if (!is_empty(move)){
			if (*move == 'N'){
				ggsclock->ext_flag = 1;
				move++;
			}
			if (ggs_int_parse(&ggsclock->ext_move, move))
				RETURN(GGS_PARSE_BAD_CLOCK);
		}
	}

	RETURN(GGS_PARSE_OK);
}

#undef RETURN

/* GGS clock printed as [ini[,[N]m1]]/[inc[,[N]m2]]/[ext[,[N]m3]] */
void ggs_clock_print(GGSClock *ggsclock, FILE *f){
	if (ggsclock->ini_time > 0.0 || ggsclock->ini_move > 0 || ggsclock->ini_flag)
		ggs_time_print(ggsclock->ini_time, f);
	if (ggsclock->ini_move > 0 || ggsclock->ini_flag){
		fputc(',',f);
		if (ggsclock->ini_flag) fputc('N',f);
		fprintf(f, "%d", ggsclock->ini_move);
	}
	fputc('/',f);
	if (ggsclock->inc_time > 0.0 || ggsclock->inc_move > 0 || ggsclock->inc_flag)
		ggs_time_print(ggsclock->inc_time, f);
	if (ggsclock->inc_move > 0 || ggsclock->inc_flag){
		fputc(',',f);
		if (ggsclock->inc_flag) fputc('N',f);
		fprintf(f, "%d", ggsclock->inc_move);
	}
	fputc('/',f);
	if (ggsclock->ext_time > 0.0 || ggsclock->ext_move > 0 || ggsclock->ext_flag)
		ggs_time_print(ggsclock->ext_time, f);
	if (ggsclock->ext_move > 0 || ggsclock->ext_flag){
		fputc(',',f);
		if (ggsclock->ext_flag) fputc('N',f);
		fprintf(f, "%d", ggsclock->ext_move);
	}
}

/* parse player name */
int ggs_player_name_parse(GGSPlayer *player, const char *name){
	if (name == NULL || *name == '\0') return GGS_PARSE_BAD_PLAYER;
	ggs_string_copy(&(player->name), name);

	return GGS_PARSE_OK;
}

/* parse player name & rating */
int ggs_player_set(GGSPlayer *player, const char *name, const char *rating){
	char *word;

	if (name == NULL || *name == '\0')
		return GGS_PARSE_BAD_PLAYER;

	word = malloc((strlen(name) + 1)*sizeof(char));
	if (word == NULL)
		return GGS_PARSE_BAD_PLAYER;
	ggs_parse(name, word);
	ggs_string_copy(&(player->name), word);
	free(word);

	if (ggs_double_parse(&(player->rating), rating))
		return GGS_PARSE_BAD_PLAYER;

	return GGS_PARSE_OK;
}

void ggs_player_clean(GGSPlayer *player){
	if (player != NULL){
		free(player->name);
		player->name = NULL;
	}
}

void ggs_match_clean(GGSMatch *match){
	if (match != NULL){
		free(match->id);
		match->id = NULL;
		ggs_player_clean(match->player);
		ggs_player_clean(match->player + 1);
	}
}

void ggs_request_clean(GGSRequest* request){
	if (request != NULL){
		free(request->id);
		request->id = NULL;
		ggs_player_clean(request->player);
		ggs_player_clean(request->player + 1);
	}
}

/* print a player name */
void ggs_player_print(GGSPlayer *player, FILE *f){
	fprintf(f, "%s (%6.1f)", player->name,  player->rating);
}

/* parse match type */
int ggs_match_type_parse(GGSMatchType *type, const char *word){
	const char *c;

	type->is_synchro = 0;
	type->is_saved = 0;
	type->is_rated = 0;
	type->is_komi = 0;
	type->is_rand = 0;
	type->is_anti = 0;
	type->discs = 0;
	type->size = 0;

	if (word == NULL) return GGS_PARSE_OK;

	if (*word == 's'){
		type->is_synchro = 1;
		type->size = atoi(word + 1);
	} else {
		type->size = atoi(word);
	}
	for (c = word; *c; c++){
		if (*c == 'k') type->is_komi = 1;
		if (*c == 'r'){
			type->is_rand = 1;
			type->discs = atoi(c + 1);
		}
		if (*c == 'a') type->is_anti = 1;
	}

	return GGS_PARSE_OK;
}

void ggs_match_type_print(GGSMatchType *type, FILE *f){
	if (type->is_synchro) putc('s', f);
	fprintf(f,"%d", type->size);
	if (type->is_anti) putc('a', f);
	if (type->is_rand){
		putc('r', f);
		fprintf(f,"%d", type->discs);
	}
	if (type->is_komi) putc('k', f);
	if (type->is_rated){
		putc(' ', f);
		if (type->is_saved) putc('S', f);
		else putc('R', f);
	}
}

/* check if I am concerned by the match */
int ggs_match_check_destination(GGSMatch *match, const char *me){
	return (strcmp(me, match->player[0].name) == 0 || strcmp(me, match->player[1].name) == 0);
}

/* check if I am concerned by the match request */
int ggs_request_check_destination(GGSRequest *request, const char *me){
	return (strcmp(me, request->player[0].name) != 0 && strcmp(me, request->player[1].name) == 0);
}

#define RETURN(return_value) {\
	free(word); \
	return return_value; \
}

int ggs_request_parse(GGSRequest *request, GGSText *text){
	const char *line = text->line[0];
	int l = strlen(line) + 1;
	char *word = malloc(l);

	if (is_empty(line)) RETURN(GGS_PARSE_BAD_REQUEST);
	line = ggs_parse(line, word);
	if (strcmp("/os:", word) != 0) RETURN(GGS_PARSE_BAD_REQUEST);

	if (is_empty(line)) RETURN(GGS_PARSE_BAD_REQUEST);
	line = ggs_parse(line, word);
	if (strcmp("+", word) != 0) RETURN(GGS_PARSE_BAD_REQUEST);

	if (is_empty(line)) RETURN(GGS_PARSE_BAD_REQUEST);
	line = ggs_parse(line, word);
	ggs_string_copy(&(request->id), word);
	
	RETURN(GGS_PARSE_OK);
}

void ggs_request_print(GGSRequest *request, FILE *f){
	fprintf(f, "GGS request for match %s ", request->id);
	ggs_match_type_print(request->match_type, f);
	fputc('\n', f);

	fputc('\t', f);
	ggs_player_print(request->player, f);
	fputc(' ', f);
	ggs_clock_print(request->clock, f);
	fputc('\n', f);

	fputc('\t', f);
	ggs_player_print(request->player + 1, f);
	fputc(' ', f);
	ggs_clock_print(request->clock + 1, f);
	fputc('\n', f);
}

int ggs_match_on_parse(GGSMatchOn *match, GGSText *text){
	const char *line = text->line[0];
	int l = strlen(line) + 1;
	char *word = malloc(l * sizeof(char));

	if (is_empty(line)) RETURN(GGS_PARSE_BAD_MATCH_ON);
	line = ggs_parse(line, word);
	if (strcmp("/os:", word) != 0) RETURN(GGS_PARSE_BAD_MATCH_ON);

	if (is_empty(line)) RETURN(GGS_PARSE_BAD_MATCH_ON);
	line = ggs_parse(line, word);
	if (strcmp("+", word) != 0) RETURN(GGS_PARSE_BAD_MATCH_ON);

	if (is_empty(line)) RETURN(GGS_PARSE_BAD_MATCH_ON);
	line = ggs_parse(line, word);
	if (strcmp("match", word) != 0) RETURN(GGS_PARSE_BAD_MATCH_ON);

	if (is_empty(line)) RETURN(GGS_PARSE_BAD_MATCH_ON);
	line = ggs_parse(line, word);
	ggs_string_copy(&(match->id), word);

	if (is_empty(line)) RETURN(GGS_PARSE_BAD_MATCH_ON);
	line = ggs_parse(line, word);
	if (ggs_player_set(&(match->player[0]), line, word))
		RETURN(GGS_PARSE_BAD_MATCH_ON);
	line = ggs_skip_word(line);

	if (is_empty(line)) RETURN(GGS_PARSE_BAD_MATCH_ON);
	line = ggs_parse(line, word);
	if (ggs_player_set(&(match->player[1]), line, word))
		RETURN(GGS_PARSE_BAD_MATCH_ON);
	line = ggs_skip_word(line);

	if (is_empty(line)) RETURN(GGS_PARSE_BAD_MATCH_ON);
	line = ggs_parse(line, word);
	ggs_match_type_parse(match->match_type, word);

	if (is_empty(line)) RETURN(GGS_PARSE_BAD_MATCH_ON);
	line = ggs_parse(line, word);
	match->match_type->is_rated = (strcmp(word, "R") == 0) ? 1 : 0;

	RETURN(GGS_PARSE_OK);
}

void ggs_match_on_print(GGSMatchOn *match_on, FILE *f){
	fprintf(f, "GGS match on %s ", match_on->id);
	ggs_match_type_print(match_on->match_type, f);
	fputc('\n', f);

	fputc('\t', f);
	ggs_player_print(match_on->player, f);
	fputc('\n', f);

	fputc('\t', f);
	ggs_player_print(match_on->player + 1, f);
	fputc('\n', f);
}

int ggs_match_off_parse(GGSMatchOff *match, GGSText *text){
	const char *line = text->line[0];
	int l = strlen(line) + 1;
	char *word = malloc(l * sizeof(char));

	if (is_empty(line)) RETURN(GGS_PARSE_BAD_MATCH_OFF);
	line = ggs_parse(line, word);
	if (strcmp("/os:", word) != 0) RETURN(GGS_PARSE_BAD_MATCH_OFF);

	if (is_empty(line)) RETURN(GGS_PARSE_BAD_MATCH_OFF);
	line = ggs_parse(line, word);
	if (strcmp("-", word) != 0) RETURN(GGS_PARSE_BAD_MATCH_OFF);

	if (is_empty(line)) RETURN(GGS_PARSE_BAD_MATCH_OFF);
	line = ggs_parse(line, word);
	if (strcmp("match", word) != 0) RETURN(GGS_PARSE_BAD_MATCH_OFF);

	if (is_empty(line)) RETURN(GGS_PARSE_BAD_MATCH_OFF);
	line = ggs_parse(line, word);
	ggs_string_copy(&(match->id), word);

	if (is_empty(line)) RETURN(GGS_PARSE_BAD_MATCH_ON);
	line = ggs_parse(line, word);
	if (ggs_player_set(&(match->player[0]), line, word))
		RETURN(GGS_PARSE_BAD_MATCH_ON);
	line = ggs_skip_word(line);

	if (is_empty(line)) RETURN(GGS_PARSE_BAD_MATCH_ON);
	line = ggs_parse(line, word);
	if (ggs_player_set(&(match->player[1]), line, word))
		RETURN(GGS_PARSE_BAD_MATCH_ON);
	line = ggs_skip_word(line);

	RETURN(GGS_PARSE_OK);
}

void ggs_match_off_print(GGSMatchOff *match_off, FILE *f){
	fprintf(f, "GGS match off %s ", match_off->id);
	fputc('\n', f);

	fputc('\t', f);
	ggs_player_print(match_off->player, f);
	fputc('\n', f);

	fputc('\t', f);
	ggs_player_print(match_off->player + 1, f);
	fputc('\n', f);
}

int ggs_board_parse(GGSBoard *board, GGSText *text){
	const char *line = text->line[0];
	char *word = malloc(LINE_SIZE);
	int ii = 1, i, j;

	if (text->n_lines < 17) RETURN(GGS_PARSE_BAD_BOARD);

	if (is_empty(line)) RETURN(GGS_PARSE_BAD_BOARD);

	line = ggs_parse(line, word);
	if (strcmp("/os:", word) != 0) RETURN(GGS_PARSE_BAD_BOARD);

	if (is_empty(line)) RETURN(GGS_PARSE_BAD_BOARD);
	line = ggs_parse(line, word);
	board->is_join = (strcmp("join", word) == 0);
	board->is_update = (strcmp("update", word) == 0);

	if ( !board->is_update && !board->is_join){
		RETURN(GGS_PARSE_BAD_BOARD);
	}

	if (is_empty(line)) RETURN(GGS_PARSE_BAD_BOARD);
	line = ggs_parse(line, word);
	ggs_string_copy(&(board->id), word);

	if (is_empty(line)) RETURN(GGS_PARSE_BAD_BOARD);
	line = ggs_parse(line, word);
	ggs_match_type_parse(board->match_type, word);

	if (!is_empty(line)){
		line = ggs_parse(line, word);
		if (word[1] == '?'){
			board->match_type->is_komi = 0;
			board->komi = 0;
		} else {
			board->match_type->is_komi = 1;
			ggs_double_parse(&board->komi, word + 1);
		}
	}

	if (board->is_join){
		if (ggs_int_parse(&board->move_list_n, text->line[1] + 1))
			RETURN(GGS_PARSE_BAD_BOARD);
		if (board->move_list_n > 0){
			if (text->n_lines < board->move_list_n + 30)
				RETURN(GGS_PARSE_BAD_BOARD);
			ii = 5;
			for (i = 0; i < 8; i++){
	    		++ii;
				for (j = 0; j < 8; j++)
					board->board_init[i * 8 + j] = text->line[ii][4 + j * 2];
			}
			ii += 3;
			board->turn_init = text->line[ii][1];

			for (i = 0; i < board->move_list_n; i++){
				++ii;
				ggs_parse_until(text->line[ii] + 6, word, '/');
				ggs_move_parse(board->move_list + i, word);
			}
		} else ii = 2;
	}
	if (text->n_lines < ii + 14)
		RETURN(GGS_PARSE_BAD_BOARD);

	line = ggs_skip_word(text->line[ii]);
	line = ggs_parse(line ,word);
	ggs_int_parse(&board->move_no, word);
	ggs_move_parse(&board->move, line);

	++ii;
	line = ggs_parse(text->line[ii] ,word);
	if (ggs_player_set(board->player, word + 1, line + 1))
		RETURN(GGS_PARSE_BAD_BOARD);
	line = ggs_skip_word(line + 1);
	line = ggs_parse(line ,word);
	board->color[0] = word[0];
	if (ggs_clock_parse(board->clock, line))	RETURN(GGS_PARSE_BAD_BOARD);

	++ii;
	line = ggs_parse(text->line[ii] ,word);
	if (ggs_player_set(board->player + 1, word + 1, line + 1))
		RETURN(GGS_PARSE_BAD_BOARD);
	line = ggs_skip_word(line + 1);
	line = ggs_parse(line ,word);
	board->color[1] = word[0];
	if (ggs_clock_parse(board->clock + 1, line)) RETURN(GGS_PARSE_BAD_BOARD);

	ii += 2;

	for (i = 0; i < 8; i++){
	    ++ii;
		for (j = 0; j < 8; j++)
			board->board[i * 8 + j] = text->line[ii][4 + j * 2];
	}
	ii += 3;
	board->turn = text->line[ii][1];

	if (board->is_join && board->move_list_n == 0){
		memcpy(board->board_init, board->board, 64);
		board->turn_init = board->turn;
	}

	RETURN(GGS_PARSE_OK);
}

void ggs_board_print(GGSBoard *board, FILE *f){
	Board b[1];
	char board_string[80];
	int i;
	const char *sep = "\n---------------------------------------------------------\n";

	fputs(sep, f);
	fprintf(f, "GGS board %s ", board->id);
	ggs_match_type_print(board->match_type, f);
	fprintf(f, "\n\t%s (%6.1f %c) ", board->player[0].name,  board->player[0].rating, board->color[0]);
	ggs_clock_print(board->clock, f);
	fprintf(f, "\n\t%s (%6.1f %c) ", board->player[1].name,  board->player[1].rating, board->color[1]);
	ggs_clock_print(board->clock + 1, f);
	if (board->move_list_n > 0){
		fprintf(f, "\ninitial board = \n");
		sprintf(board_string, "%s %c", board->board_init, board->turn_init);
		board_set(b, board_string);
		board_print(b, board->turn, f);
		fputc('\n', f);
		fprintf(f, "moves (%d) :", board->move_list_n);
		for (i = 0; i < board->move_list_n; i++){
			fputc(' ', f);
			move_print(board->move_list[i], b->player, f);
		}
	}
	fprintf(f, "\ncurrent board = \n");
	sprintf(board_string, "%s %c", board->board, board->turn);
	board_set(b, board_string);
	board_print(b, board->turn, f);
	fputs(sep, f);
	fputc('\n', f);
}

#undef RETURN

int ggs_os_on_parse(GGSText *text)
{
	if (strcmp(text->line[0], ": + /os 1") == 0) return GGS_PARSE_OK;
	return GGS_PARSE_BAD_OS_ON;
}

int ggs_os_off_parse(GGSText *text)
{
	if (strcmp(text->line[0], ": - /os 1") == 0) return GGS_PARSE_OK;
	return GGS_PARSE_BAD_OS_OFF;
}

int ggs_saio_delay_parse(GGSText *text, long long *delay)
{
	char *s = strstr(text->line[0], "Sorry, i will accept new games in");

	if (s && strlen(s) > 35) {
		*delay = string_to_int(s + 34, 300) * 1000 + real_clock();		
		return GGS_PARSE_OK;
	}
	return GGS_PARSE_BAD_SAIO_DELAY;
}

int ggs_admin_parse(GGSAdmin *admin, GGSText *text)
{
	int i;
	const char *line;
	char *word;
	
	word = malloc(strlen(text->line[0]) + 1);
	if (word) {
		line = ggs_parse(text->line[0], word);
		
		for (i = 0; !is_empty(admin_list[i]); i++) {
			if (strncmp(admin_list[i], word, strlen(admin_list[i])) == 0) {
				ggs_string_copy(&admin->command, line);
				strcpy(admin->name, admin_list[i]);
				free(word);
				return GGS_PARSE_OK;
			}
		}
		free(word);
	}
	return GGS_PARSE_BAD_ADMIN;
}

/* Free client's resources */
void ggs_client_free(GGSClient *client){
	ggs_client_disconnect(client);
	ggs_match_clean((GGSMatch*)client->board);
	ggs_match_clean((GGSMatch*)client->request);
	ggs_match_clean((GGSMatch*)client->match_on);
	ggs_match_clean((GGSMatch*)client->match_off);
	ggs_request_clean(client->request);
	log_close(ggs_log);
}

void ggs_client_connect(GGSClient *client, const char *host, int port){
	int flag, value;
	struct linger ling = {0, 0};

	log_open(ggs_log, "log/ggs.log");

	memset(client, 0, sizeof (GGSClient)); // TODO: dangerous
	client->type = GGS_UNDEF;
	client->recv_buffer = calloc(16, sizeof (char));
	client->send_buffer = calloc(16, sizeof (char));
	ggs_text_init(&client->message),
	client->last_refresh = real_clock();
	client->is_playing = false;

	client->sock = socket_connect(host, port);
	if (client->sock < 0){
		fatal_error("error in socket_connect %d\n", client->sock);
	}
	flag = fcntl(client->sock, F_GETFL) | O_NONBLOCK;
	fcntl(client->sock, F_SETFL, flag);

	value = 1;
	socket_option_set(client->sock, SOL_SOCKET, SO_KEEPALIVE, &value, sizeof (value));
	value = 1;
	socket_option_set(client->sock, SOL_SOCKET, SO_REUSEADDR, &value, sizeof (value));
	value = 0;
	socket_option_set(client->sock, SOL_SOCKET, SO_DONTROUTE, &value, sizeof (value));
	socket_option_set(client->sock, SOL_SOCKET, SO_LINGER, &ling, sizeof (ling));
	value = 1;
	socket_option_set(client->sock, IPPROTO_TCP, TCP_NODELAY, &value, sizeof (value));
	
}

void ggs_client_process(GGSClient *client){
	fd_set fd_s, fd_r, fd_e;
	struct timeval tv = {0, 500};
	int no;
	char line[LINE_SIZE], *end;
	GGSText *text = &client->message;

	FD_ZERO(&fd_s);
	FD_ZERO(&fd_e);
	FD_ZERO(&fd_r);

	if (strlen(client->send_buffer) > 0) FD_SET(client->sock, &fd_s);
	FD_SET(client->sock, &fd_r);
	FD_SET(client->sock, &fd_e);

	no = select(FD_SETSIZE, &fd_r, &fd_s, &fd_e, &tv);
	if (no <= 0 && strlen(client->recv_buffer) == 0) return;
	if (no < 0) {
		fatal_error("error in select %d\n", no);
	}
	if (FD_ISSET(client->sock, &fd_s)) {
		if (!socket_write(client->sock, client->send_buffer, 0)) {
			error("error in socket_write %d\nConnection lost, so I exit!\n", no);		
			client->type = GGS_QUIT; // socket closed
			return;
		}
	}

	if (FD_ISSET(client->sock, &fd_r)){
		if (!socket_read(client->sock, &client->recv_buffer, 0)){
			error("error in socket_read %d\nConnection lost, so I exit!\n", no);		
			client->type = GGS_QUIT; // socket closed
			return;
		}
	}
	if (FD_ISSET(client->sock, &fd_e)){
		if (!socket_read(client->sock, &client->recv_buffer, 0)){
			fatal_error("error in socket_read error %d\n", no);
		}
	}
	if (strlen(client->recv_buffer) == 0) return;

	/* ggs_text_init(text); */
	while (client->type == GGS_UNDEF){
		end = parse_line(client->recv_buffer, line, LINE_SIZE);
		if (end == client->recv_buffer) return; // read failed
		memmove(client->recv_buffer, end, strlen(end) + 1);
		if (is_empty(line)) continue;

		log_receive(ggs_log, "GGS", "%s\n", line);

		if (strcmp(line, "READY") == 0 || strcmp(line, "ALERT") == 0) {
			if (text->n_lines == 0) continue;
			if (ggs_match_on_parse(client->match_on, text) == GGS_PARSE_OK) {
				if (ggs_match_check_destination((GGSMatch *)client->match_on, client->me))
					client->type = GGS_MATCH_ON;
				else
					client->type = GGS_WATCH_ON;
				break;
			}
			else if (ggs_match_off_parse(client->match_off, text) == GGS_PARSE_OK) {
				if (ggs_match_check_destination((GGSMatch *)client->match_off, client->me))
					client->type = GGS_MATCH_OFF;
				else
					client->type = GGS_WATCH_OFF;
				break;
			}
			else if (ggs_board_parse(client->board, text) == GGS_PARSE_OK) {
				if (ggs_match_check_destination((GGSMatch *)client->board, client->me))
					client->type = GGS_BOARD;
				else
					client->type = GGS_WATCH_BOARD;
				break;
			}
			else if (ggs_os_on_parse(text) == GGS_PARSE_OK) {
				client->type = GGS_OS_ON;
				break;
			}
			else if (ggs_os_off_parse(text) == GGS_PARSE_OK) {
				client->type = GGS_OS_OFF;
				break;
			}
			else if (ggs_request_parse(client->request, text) == GGS_PARSE_OK) {
				// if (ggs_request_check_destination(client->request, client->me))
					client->type = GGS_REQUEST_ON;
			 	break;
			} else if (ggs_admin_parse(client->admin, text) == GGS_PARSE_OK) {
				client->type = GGS_ADMIN_CMD;
				break;
			} else if (ggs_saio_delay_parse(text, &client->once->delay) == GGS_PARSE_OK) {
				client->type = GGS_SAIO_DELAY;
			}
			ggs_text_init(text);
		} else {
			ggs_text_add(text, line);
		}
	}
}

void ggs_client_disconnect(GGSClient *client){
	socket_shutdown(client->sock, 2);
	free(client->recv_buffer);
	free(client->send_buffer);
	client->recv_buffer = NULL;
	client->send_buffer = NULL;
}

void ggs_client_send(GGSClient *client, const char *fmt, ...){
	int buffer_length = 0, message_length;
 	va_list ap;

	if (fmt == NULL) return;
	
	va_start(ap, fmt);
	message_length = vsnprintf(NULL, 0, fmt, ap);
	va_end(ap);

	if (client->send_buffer != NULL) buffer_length = strlen(client->send_buffer);

	client->send_buffer = realloc(client->send_buffer, buffer_length + message_length + 1);

	va_start(ap, fmt);
	vsnprintf(client->send_buffer + buffer_length, message_length + 1, fmt, ap);
	va_end(ap);

	log_send(ggs_log, "GGS", "%s", client->send_buffer + buffer_length);
	printf("GGS> %s", client->send_buffer + buffer_length);
}

void ggs_client_clean(GGSClient *client)
{
	client->type = GGS_UNDEF;
	ggs_text_init(&client->message);
}

int ggs_client_ready(GGSClient *client)
{
	return client->type;
}

void ggs_observer(Result *result) 
{
		search_observer(result);
}

void ggs_client_refresh(GGSClient *client) 
{
	if (real_clock() - client->last_refresh > 60000) { // 60 sec.
		if (client->is_playing) ggs_client_send(client, "tell /os open 0\n" );
		else ggs_client_send(client, "tell /os open %d\n", options.ggs_open);
		ggs_client_send(client, "tell /os continue\n");
		client->last_refresh = real_clock();
	}	

	if (client->loop->delay != 0 && real_clock() - client->loop->delay > 0) {
		client->loop->delay = 0;
		ggs_client_send(client, "%s\n", client->loop->cmd);
	}	

	if (client->once->cmd && client->once->delay != 0 && real_clock() - client->once->delay > 0) {
		client->once->delay = 0;
		ggs_client_send(client, "%s\n", client->once->cmd);
	}
}

static void ui_login(UI *ui) {
	
	/* sanity check */
	if (options.ggs_host == NULL) fatal_error("Unknown GGS host\n");
	if (options.ggs_port == NULL) fatal_error("Unknown GGS port\n");
	if (options.ggs_login == NULL) fatal_error("Unknown GGS login\n");
	if (options.ggs_password == NULL) fatal_error("Unknown GGS password\n");
	if (strlen(options.ggs_login) > 8) fatal_error("Bad GGS login %s (too much characters)\n", options.ggs_login);

	ggs_client_connect(ui->ggs, options.ggs_host, atoi(options.ggs_port));
	relax(2000); /* wait 2 secs */
	ggs_client_send(ui->ggs, "%s\n", options.ggs_login);
	ggs_client_send(ui->ggs, "%s\n", options.ggs_password);
	ui->ggs->me = options.ggs_login;

	relax(2000); /* wait 2 secs */
	ggs_client_send(ui->ggs, "vt100 -\n");
	ggs_client_send(ui->ggs, "bell -t -tc -tg -n -nc -ng -ni -nn\n");
	ggs_client_send(ui->ggs, "verbose -news -faq -help -ack\n");
	ggs_client_send(ui->ggs, "chann %%\n");
	ggs_client_send(ui->ggs, "chann + .chann\n");
	ggs_client_send(ui->ggs, "chann + .chat\n");
	ggs_client_send(ui->ggs, "chann + .%s\n", ui->ggs->me);
	ggs_client_send(ui->ggs, "tell .%s Hello!\n", ui->ggs->me);
	ggs_client_send(ui->ggs, "notify + /os\n");

	ui->ggs->loop->cmd = NULL;
	ui->ggs->loop->i = 0;
	ui->ggs->loop->delay = 0;

	ui->ggs->once->cmd = NULL;
	ui->ggs->once->delay = 0;
}

/**
 * @brief Search and play a move.
 */
void ui_ggs_ponder(UI *ui, int turn) {
	Play *play;

	if (search_count_tasks(ui->play->search) == options.n_task) {
		play = ui->play;
	} else  {
		play = ui->play + turn;
	}

	play_ponder(play);
}

/**
 * @brief Search and play a move.
 */
void ui_ggs_play(UI *ui, int turn) {
	long long real_time = -time_clock();
	int remaining_time = ui->ggs->board->clock[turn].ini_time;
	int extra_time = ui->ggs->board->clock[turn].ext_time;
	Play *play;
	Result *result;
	char move[4], line[32];
	char *(search_state_array[6]) = {"running", "interrupted", "stop pondering", "out of time", "stopped on user demand", "completed"};
	char search_state[32];

	if (ui->is_same_play) {
		play = ui->play;
		if (search_count_tasks(ui->play->search) < options.n_task) {
			printf("<use a single %d tasks search while a single game is played>\n", options.n_task);
			play_stop_pondering(ui->play);
			search_set_task_number(ui->play[0].search, options.n_task);
			play_stop_pondering(ui->play + 1);
			search_set_task_number(ui->play[1].search, 0);
		}
	} else {
		play = ui->play + turn;
		if (search_count_tasks(ui->play->search) == options.n_task && options.n_task > 1) {
			printf("<split single %d tasks search into two %d task searches>\n", options.n_task, options.n_task / 2);
			play_stop_pondering(ui->play);
			search_set_task_number(ui->play[0].search, options.n_task / 2);
			play_stop_pondering(ui->play + 1);
			search_set_task_number(ui->play[1].search, options.n_task / 2);
			search_share(ui->play[0].search, ui->play[1].search);
			ui_ggs_ponder(ui, turn ^ 1); // ponder opponent move
		}
	}

	// game over detection...
	if (play_is_game_over(play)) {
		ggs_client_send(ui->ggs, "tell .%s *** GAME OVER ***\n", ui->ggs->me);
		return;
	}

	result = play->result;
	
	if (remaining_time > 60) remaining_time -= 10; // keep 10s. for safety.
	else if (remaining_time > 10) remaining_time -= 2; // keep 2s. for safety.
	if (remaining_time < 0) remaining_time = 1;
	play_adjust_time(play, remaining_time, extra_time);

	printf("<ggs: go thinking>\n");
	play_go(play, false);

	real_time += time_clock();
	real_time = MAX(real_time - 500, 0);

	move_to_string(result->move, play->player, move);

	ggs_client_send(ui->ggs, "tell /os play %s %s/%d/%.2f\n", ui->ggs->board->id, move, result->score, 0.001 * (real_time + 1));

	if (result->book_move == true) {
		printf("[%s plays %s in game %s ; score = %d from book]\n", ui->ggs->me, move, ui->ggs->board->id, result->score);
		ggs_client_send(ui->ggs, "tell .%s -----------------------------------------"
			"\\%s plays %s in game %s"
			"\\score == %d from book\n",
			ui->ggs->me,
			ui->ggs->me, move, ui->ggs->board->id,
			result->score
		);
	} else if (play->search->n_empties >= 15) { //avoid noisy display
		char *bound;

		if (result->bound[result->move].lower < result->score && result->score == result->bound[result->move].upper) bound = "<=";
		else if (result->bound[result->move].lower == result->score && result->score < result->bound[result->move].upper) bound = ">=";
		else bound = "==";

		info("<%s plays %s in game %s ; score = %d at %d@%d%% ; %lld nodes in %.1fs (%.0f nodes/s.)>\n",
			ui->ggs->me, move, ui->ggs->board->id,
			result->score, result->depth, selectivity_table[result->selectivity].percent,
			result->n_nodes, 0.001 * real_time, (result->n_nodes / (0.001 * real_time + 0.001))
		);
		if (play->search->stop == STOP_TIMEOUT) {
			sprintf(search_state, "%s at %d@%d%%", search_state_array[play->search->stop], play->search->depth, selectivity_table[play->search->selectivity].percent);
		} else {
			sprintf(search_state, "%s", search_state_array[play->search->stop]);
		}
		ggs_client_send(ui->ggs, "tell .%s -----------------------------------------"
			"\\%s plays %s in game %s using %d thread%s"
			"\\score %s %+02d at %d@%d%% ; PV: %s ;"
			"\\nodes: %'lld ; time: search = %.1fs, move = %.1fs; speed: %'.0f n/s."
			"\\search %s\n",
			ui->ggs->me,
			ui->ggs->me, move, ui->ggs->board->id, search_count_tasks(play->search), search_count_tasks(play->search) > 1 ? "s ;" : " ;",
			bound, result->score,
			result->depth, selectivity_table[result->selectivity].percent,
			line_to_string(result->pv, 8, " ", line),
			result->n_nodes, 0.001 * result->time, 0.001 * real_time, (result->n_nodes / (0.001 * result->time+ 0.001)),
			search_state
		);
	}
}

/**
 * @brief Join a new game.
 */
void ui_ggs_join(UI *ui) {
	char buffer[256];
	char s_move[4];
	Play *play;
	int edax_turn, i;
	
	printf("[received GGS_BOARD_JOIN]\n");

	// set correct played game
	if (strcmp(ui->ggs->board->player[0].name, ui->ggs->me) == 0) {
		play = ui->play;
		edax_turn = BLACK;
	} else if (strcmp(ui->ggs->board->player[1].name, ui->ggs->me) == 0) {
		play = ui->play + 1;
		edax_turn = WHITE;
	} else {
		warn("Edax is not concerned by this game\n");
		return;
	}

	// non synchro games => play a single match
	if (!ui->ggs->board->match_type->is_synchro) play = ui->play;

	// set board
	sprintf(buffer, "%s %c", ui->ggs->board->board_init, ui->ggs->board->turn_init);
	play_set_board(play, buffer);

	for(i = 0; i < ui->ggs->board->move_list_n; i++){
		if (!play_move(play, ui->ggs->board->move_list[i])) {
			error("cannot play GGS move %s ?", move_to_string(ui->ggs->board->move_list[i], play->player, s_move));
			break;
		}
	}
	printf("[%s's turn in game %s]\n", ui->ggs->board->player[play->player].name, ui->ggs->board->id);
	board_print(play->board, play->player, stdout);

	ui->is_same_play = (ui->ggs->board->move_list_n == 0 || board_equal(ui->play[0].board, ui->play[1].board) || !ui->ggs->board->match_type->is_synchro);
	if (ui->is_same_play) printf("[Playing same game]\n");

	// set time & start thinking
	if (play->player == edax_turn) {
		printf("<My turn>\n");
		ggs_client_send(ui->ggs, "tell .%s =====================================\n", ui->ggs->me);
		ui_ggs_play(ui, edax_turn);
		ui_ggs_ponder(ui, edax_turn);
	} else {
		printf("[Waiting opponent move]\n");
//		ui_ggs_ponder(ui, edax_turn);
	}
}

/**
 * @brief Update a game.
 */
void ui_ggs_update(UI *ui) {
	char buffer[256], s_move[4];
	Play *play;
	int edax_turn, turn;
	Board board[1];
	
	printf("[received GGS_BOARD_UPDATE]\n");

	// set correct played game
	if (strcmp(ui->ggs->board->player[0].name, ui->ggs->me) == 0) {
		play = ui->play;
		edax_turn = BLACK;
	} else if (strcmp(ui->ggs->board->player[1].name, ui->ggs->me) == 0) {
		play = ui->play + 1;
		edax_turn = WHITE;
	} else {
		return;
	}

	if (!ui->ggs->board->match_type->is_synchro) play = ui->play;
		

	// set board as an edax's board
	sprintf(buffer, "%s %c", ui->ggs->board->board, ui->ggs->board->turn);
	turn = board_set(board, buffer);

	// update the board... if possible
	if (!play_move(play, ui->ggs->board->move)) {
		info("<Updating with bad move %s>\n", move_to_string(ui->ggs->board->move, play->player, s_move));
	}

	if (!board_equal(board, play->board)) { // may happens when game diverges
		info("<Resynchronize boards: diverging games>\n");
		*play->board = *board; play->player = turn;
	}

	if (turn != play->player) { // should never happen: TODO fatal error?
		printf("[WARNING: updating player's turn]\n");
		play->player = turn;
	}

	printf("[%s's turn in game %s]\n", ui->ggs->board->player[play->player].name, ui->ggs->board->id);

	// playing same game... ?
	ui->is_same_play = (!ui->ggs->board->match_type->is_synchro || board_equal(ui->play[0].board, ui->play[1].board));
	if (ui->is_same_play) printf("<Playing same game...>\n");

	// set time & start thinking
	if (play->player == edax_turn) {
		printf("<My turn>\n");
		ui_ggs_play(ui, edax_turn);
	} else {
		printf("<Opponent turn>\n");
		ui_ggs_ponder(ui, edax_turn);
	}
}

bool ui_ggs_accept(UI *ui) {
	const GGSRequest *request = ui->ggs->request;

	if (request->match_type->size != 8) return false;
	if (request->match_type->is_anti) return false;
	if (request->match_type->is_komi) return false;

	ggs_client_send(ui->ggs, "tell /os accept %s\n", request->id);

	return true;
}

void ui_init_ggs(UI *ui) {
	const int n_task = options.n_task;

	play_init(ui->play, ui->book);
	ui->book->search = ui->play->search;
	book_load(ui->book, options.book_file);

	ui->play[0].search->id = 1;
	options.n_task = 1;
	play_init(ui->play + 1, ui->book);
	ui->play[1].search->id = 2;
	options.n_task = n_task;
	log_open(ggs_log, options.ggs_log_file);

	ui_login(ui);
}

void ui_loop_ggs(UI *ui){
	char *cmd = NULL, *param = NULL;

	ui_event_init(ui);

	ui->mode = 3;

	for (;;) {
		relax(1);

		if (ui_event_peek(ui, &cmd, &param)) {
			if (strcmp(cmd, "stop") == 0) { /* stop the search */
				if (ui->play[0].state == IS_THINKING) play_stop(ui->play);
				else if (ui->play[1].state == IS_THINKING) play_stop(ui->play + 1);				
			} else if (strcmp(cmd, "loop") == 0) { /* repeat a cmd <n> times */
				free(ui->ggs->loop->cmd);
				errno = 0;
				ui->ggs->loop->cmd = string_duplicate(parse_int(param, &ui->ggs->loop->i));
				if (errno) ui->ggs->loop->i = 100;
				if (ui->ggs->loop->i > 0) {
					info("<loop %d>\n", ui->ggs->loop->i);
					--ui->ggs->loop->i;
					ggs_client_send(ui->ggs, "%s\n", ui->ggs->loop->cmd);
				}
			} else {
				ggs_client_send(ui->ggs, "%s %s\n", cmd, param);
			}
		}
		
		ggs_client_refresh(ui->ggs);

		ggs_client_process(ui->ggs);
		
		switch(ggs_client_ready(ui->ggs)) {

		case GGS_OS_ON:
			printf("[received GGS_OS_ON]\n");
			ggs_client_clean(ui->ggs);
			ggs_client_send(ui->ggs, "tell /os trust +\n" );
			ggs_client_send(ui->ggs, "tell /os rated +\n" );
			ggs_client_send(ui->ggs, "tell /os request +\n" );
			ggs_client_send(ui->ggs, "tell /os client -\n" );
			ggs_client_send(ui->ggs, "tell /os open %d\n", options.ggs_open);
			ggs_client_send(ui->ggs, "mso\n" );
		break;

		case GGS_MATCH_ON:
			printf("[received GGS_MATCH_ON]\n");
			ui->ggs->is_playing = true;
			ggs_client_clean(ui->ggs);
			ggs_client_send(ui->ggs, "tell /os open 0\n" );
			break;

		case GGS_MATCH_OFF:
			printf("[received GGS_MATCH_OFF]\n");

			if (!ui->ggs->match_on->match_type->is_rand) {
				if (ui->ggs->match_on->match_type->is_synchro) {
					printf("[store match #1]\n");
					play_store(ui->play);
					printf("[store match #2]\n");
					play_store(ui->play + 1);
				} else {
					printf("[store match]\n");
					play_store(ui->play);
				}
				if (ui->book->need_saving) {
					book_save(ui->book, options.book_file);
					ui->book->need_saving = false;
				}
			}

			ui->ggs->is_playing = false;
			ggs_client_clean(ui->ggs);
			ggs_client_send(ui->ggs, "tell /os open %d\n", options.ggs_open);
			if (ui->ggs->loop->i > 0) {
				info("<loop %d>\n", ui->ggs->loop->i);
				--ui->ggs->loop->i;
				ui->ggs->loop->delay = 10000 + real_clock(); // wait 10 sec.
			}
			break;

		case GGS_BOARD:
			ggs_client_clean(ui->ggs);
			if (ui->ggs->board->is_join) ui_ggs_join(ui);
			else ui_ggs_update(ui);

			break;

		case GGS_OS_OFF:
			printf("[received GGS_OS_OFF]\n");
			ggs_client_clean(ui->ggs);
			break;

		case GGS_REQUEST_ON:
			printf("[received GGS_REQUEST_ON]\n");
			ggs_client_clean(ui->ggs);
			//ui_ggs_accept(ui);
			break;

		case GGS_WATCH_ON:
			printf("[received GGS_WATCH_ON]\n");
			ggs_client_clean(ui->ggs);
			break;

		case GGS_WATCH_OFF:
			printf("[received GGS_WATCH_OFF]\n");
			ggs_client_clean(ui->ggs);
			break;

		case GGS_WATCH_BOARD:
			printf("[received GGS_WATCH_BOARD]\n");
			ggs_client_clean(ui->ggs);
			break;

		case GGS_ADMIN_CMD:
			printf("[received GGS_ADMIN_CMD]\n");
			ggs_client_clean(ui->ggs);	
			ggs_client_send(ui->ggs, ui->ggs->admin->command);
			ggs_client_send(ui->ggs, "\ntell %s command processed\n", ui->ggs->admin->name);
			break;

		case GGS_UNDEF:
			break;

		case GGS_SAIO_DELAY:
			printf("[received GGS_SAIO_DELAY]\n");
			ggs_client_clean(ui->ggs);
			free(ui->ggs->once->cmd); ui->ggs->once->cmd = NULL;
			if (cmd != NULL && param != NULL) {
				if (strcmp(cmd, "loop") == 0) {
					ui->ggs->once->cmd = string_duplicate(ui->ggs->loop->cmd);
				} else {
					ui->ggs->once->cmd = malloc(strlen(cmd) + strlen(param) + 3);
					sprintf(ui->ggs->once->cmd, "%s %s\n", cmd, param);
				}	
				printf("[received GGS_SAIO_DELAY, retry request in %.1f s]\n", 0.001 * (ui->ggs->once->delay - real_clock()));
			} else {
				ui->ggs->once->delay = 0;
			}
			break;

		case GGS_QUIT:
			printf("[received GGS_QUIT]\n");
			free(cmd); free(param);
			exit(EXIT_SUCCESS);
			break;

		default:
			printf("[received invalid GGS_TYPE %d]\n", ggs_client_ready(ui->ggs));
			ggs_client_clean(ui->ggs);
			break;
		}
	}
}

void ui_free_ggs(UI *ui) {
	play_free(ui->play);
	play_free(ui->play + 1);
	if (ui->book->need_saving) book_save(ui->book, options.book_file);
	book_free(ui->book);
	event_free(ui->event);
	ggs_client_free(ui->ggs);
	free(ui->ggs->loop->cmd);
	free(ui->ggs->once->cmd);
	log_close(ggs_log);
}

#else

void ui_init_ggs(UI *ui) {}
void ui_loop_ggs(UI *ui) {}
void ui_free_ggs(UI *ui) {}

#endif
